using UnityEngine;
using System.Collections.Generic;

public class Area
{
	public enum AreaType
	{
		Square,			// Area in the shape of a square
		Diamond,		// Area in the shape of a diamond (every tile in distance)
	}

	// Items that a path cannot finish on
	public enum RemoveType
	{
		None = 0,
		Characters = 1,		// Doesn't add tiles that have Characters over them
		Useables = 2,		// Doesn't add tiles that have Useables over them
		Center = 8,			// Removes center of final area
		All = 256,
	}

	// Things that will block the path
	public enum BlockType
	{
		None = 0,
		Characters = 16,
		Useables = 32,
		Walls = 64,
		Doors = 128,
		All = 256,
	}

	public static List<Tile> AttackArea(Tile _center, Character _character)
	{
		uint blockType = (uint)(RemoveType.Useables) | (uint)(BlockType.Walls | BlockType.Doors);
		return Area.GetArea(_center, 3, AreaType.Diamond, blockType);
	}
	
	public static List<Tile> WalkArea(Tile _center, int _size)
	{
		uint blockType = (uint)(RemoveType.Characters | RemoveType.Useables) | (uint)(BlockType.Walls | BlockType.Doors);
		return Area.GetArea(_center, _size, AreaType.Diamond, blockType);
	}
	
	public static List<Tile> SelectArea(Tile _center)
	{
		uint blockType = (uint)Area.RemoveType.Center | (uint)BlockType.Doors;
		return Area.GetArea(_center, 1, AreaType.Diamond, blockType);
	}

	public static List<Tile> VisibleArea(Tile _center)
	{
		uint blockType = (uint)(BlockType.Walls | BlockType.Doors);
		return Area.GetArea(_center, 5, AreaType.Square, blockType);
	}

	public static List<Tile> AroundTile(Tile _center)
	{
		uint blockType = (uint)(RemoveType.Center/* | RemoveType.Reachable*/);
		List<Tile> around = Area.GetArea(_center, 1, AreaType.Square, blockType);
		for (int i = around.Count - 1; i >= 0; i--)
		{
			if (around[i].objectOnTop != null)
			{
				around.RemoveAt(i);
			}
		}

		return around;
	}

	public static List<Tile> GetArea(Tile _center, int _size, AreaType _area, uint _check = 0)
	{
		switch (_area)
		{
		case AreaType.Square:
			return GetSquareArea(_center, _size, _check);

		case AreaType.Diamond:
			return GetDiamondArea(_center, _size, _check);
		}

		return null;
	}
	
	public static Tile Intersection(List<Tile> _a, List<Tile> _b)
	{
		foreach (Tile t1 in _a)
		{
			foreach (Tile t2 in _b)
			{
				if (t1 == t2)
				{
					return t1;
				}
			}
		}
		
		return null;
	}
	
	public static Tile ClosestIntersection(List<Tile> _a, List<Tile> _b, Interactive _inter)
	{
		List<Tile> intersection = new List<Tile>();
		foreach (Tile t1 in _a)
		{
			foreach (Tile t2 in _b)
			{
				if (t1 == t2)
				{
					intersection.Add(t1);
				}
			}
		}
		
		if (intersection.Count == 0)
		{
			return null;
		}
		if (intersection.Count == 1)
		{
			return intersection[0];
		}
		else
		{
			Tile closest = null;
			float closestDistance = float.MaxValue;
			float distance;
			
			foreach (Tile inter in intersection)
			{
				distance = Vector3.Distance(_inter.transform.position, inter.transform.position);
				if (distance < closestDistance)
				{
					closestDistance = distance;
					closest = inter;
				}
			}
			
			return closest;
		}
	}
	
	private static List<Tile> GetSquareArea(Tile _center, int _size, uint _check)
	{
		int centerX = (int)_center.transform.position.x;
		int centerY = (int)_center.transform.position.y;
		List<Tile> area = new List<Tile>();
		Tile tile;

		for (int count = 1; count <= _size; count++)
		{
			for (int i = -count; i <= count; i++)
			{
				tile = FloorManager.Instance.GetTile(centerX - count, centerY + i);
				if (tile != null && !area.Contains(tile)) area.Add(tile);

				tile = FloorManager.Instance.GetTile(centerX + count, centerY + i);
				if (tile != null && !area.Contains(tile)) area.Add(tile);

				tile = FloorManager.Instance.GetTile(centerX + i, centerY + count);
				if (tile != null && !area.Contains(tile)) area.Add(tile);

				tile = FloorManager.Instance.GetTile(centerX + i, centerY - count);
				if (tile != null && !area.Contains(tile)) area.Add(tile);
			}
		}

		List<Tile> connectedArea = new List<Tile>();
		connectedArea.Add(_center);

		List<Tile> closedSet = new List<Tile>();
		List<Tile> openSet = new List<Tile>();
		openSet.Add(_center);

		Tile current, neighbour;
		while (openSet.Count > 0)
		{
			current = openSet[0];
			openSet.RemoveAt(0);
			closedSet.Add(current);

			for (int i = 0; i < current.neighbours.Count; i++)
			{
				neighbour = current.neighbours[i];
				bool addNeighbour = RespectsConditions(current, neighbour, _check);
				
				addNeighbour &= !openSet.Contains(neighbour) && !closedSet.Contains(neighbour) && area.Contains(neighbour);
				if (addNeighbour)
				{
					openSet.Add(neighbour);
					connectedArea.Add(neighbour);
				}
			}
		}
		
		if ((_check & (uint)RemoveType.Center) != 0)
		{
			connectedArea.RemoveAt(0);
		}

		return connectedArea;
	}
	
	private static List<Tile> GetDiamondArea(Tile _center, int _size, uint _check)
	{
		List<Tile> closedSet = new List<Tile>();
		List<Tile> openSet = new List<Tile>();
		openSet.Add(_center);

		Dictionary<Tile, int> centerDistance = new Dictionary<Tile, int>();
		centerDistance.Add(_center, 0);

		Tile current;
		Tile neighbour;
		int distance;
		while (openSet.Count > 0)
		{
			current = openSet[0];
			distance = centerDistance[current];
			for (int i = 0; i < current.neighbours.Count; i++)
			{
				neighbour = current.neighbours[i];
				bool addNeighbour = RespectsConditions(current, neighbour, _check);
				
				addNeighbour &= !closedSet.Contains(neighbour);
				if (addNeighbour)
				{
					if (!centerDistance.ContainsKey(neighbour) && distance + 1 <= _size)
					{
						centerDistance.Add(neighbour, distance + 1);
						openSet.Add(neighbour);
					}

				}
			}

			openSet.Remove(current);
			closedSet.Add(current);
		}

		if ((_check & (uint)RemoveType.Center) != 0)
		{
			closedSet.RemoveAt(0);
		}

		return closedSet;
	}

	public static bool RespectsConditions(Tile _current, Tile _neighbour, uint _check)
	{
		bool addNeighbour = true;
		if ((_check & (uint)RemoveType.Characters) != 0)
		{
			addNeighbour &= _neighbour.objectOnTop == null || !(_neighbour.objectOnTop is Character);
		}
		
		if ((_check & (uint)RemoveType.Useables) != 0)
		{
			addNeighbour &= _neighbour.objectOnTop == null || !(_neighbour.objectOnTop is Useable);
		}
		
		if ((_check & (uint)BlockType.Walls) != 0)
		{
			addNeighbour &= _current.IsNeighbour(_neighbour);
		}

		if ((_check & (uint)BlockType.Doors) != 0)
		{
			addNeighbour &= _current.IsConnectionOpen(_neighbour);
		}

		return addNeighbour;
	}
}
